package com.atguigu.gmall.search.service.impl;
import com.atguigu.gmall.search.entiy.Goods;
import com.atguigu.gmall.search.entiy.SearchAttr;
import com.atguigu.gmall.search.repository.GoodsRepository;
import com.atguigu.gmall.search.service.SearchBizService;
import com.atguigu.gmall.search.vo.*;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author:ahang
 * @create:2022-10-13 0:25
 * @Description:
 */
@Service
public class SearchBizServiceImpl implements SearchBizService {

    @Autowired
    GoodsRepository goodsRepository;

    @Autowired
    ElasticsearchRestTemplate esTemplate;
    /**
     * 保存商品到es中
     * @param goods
     */
    @Override
    public void onSale(Goods goods) {
        goodsRepository.save(goods);
    }

    /**
     * 从es中删除商品
     * @param skuId
     * @return
     */
    @Override
    public void cancelSale(Long skuId) {
        goodsRepository.deleteById(skuId);
    }

    /**
     * 商品检索
     * @param paramVo
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParamVo paramVo) {
        //1.根据检索条件,构造一个query对象
        Query query = buildQuery(paramVo);

        //2.检索 获取searchhits结果
        SearchHits<Goods> search = esTemplate.search(query, Goods.class, IndexCoordinates.of("goods"));

        //3.根据es得到的结果,封装为响应前端的Vo对象
        SearchResponseVo responseVo = buildResponse(search,paramVo);
        return responseVo;
    }

    /**
     * 更新热度分
     * @param skuId
     * @param hotScore
     */
    @Override
    public void updateHotScore(Long skuId, Long hotScore) {
        Optional<Goods> byId = goodsRepository.findById(skuId);
        Goods goods = byId.get();
        goods.setHotScore(hotScore);
        //es在数据已经有的条件下再保存就是更新
        goodsRepository.save(goods);
    }


    /**
     * 构造检索条件
     * @param paramVo
     * @return
     */
    private Query buildQuery(SearchParamVo paramVo) {
        //1.bool
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
            //1.1 根据不同条件构造must的子查询项
        List<QueryBuilder> must = boolQuery.must();
                //按照分类查询
        if(paramVo.getCategory1Id() != null){
            must.add(QueryBuilders.termQuery("category1Id",paramVo.getCategory1Id()));
        }
        if(paramVo.getCategory2Id() != null){
            must.add(QueryBuilders.termQuery("category2Id",paramVo.getCategory2Id()));
        }
        if(paramVo.getCategory3Id() != null){
            must.add(QueryBuilders.termQuery("category3Id",paramVo.getCategory3Id()));
        }
                //按照关键字模糊查询
        if(!StringUtils.isEmpty(paramVo.getKeyword())){
            must.add(QueryBuilders.matchQuery("title",paramVo.getKeyword()));
        }
                //按照品牌检索  例如:    3:华为
        if(!StringUtils.isEmpty(paramVo.getTrademark())){
                        //使用:分割出一个数组,取数组的第一号元素
            long tmId = Long.parseLong(paramVo.getTrademark().split(":")[0]);
            must.add(QueryBuilders.termQuery("tmId",tmId));
        }
                //按照平台属性检索  例如:   props=4:256GB:机身存储 & props=1:4500-11999:价格
        if(paramVo.getProps() != null && paramVo.getProps().length > 0){
            for (String prop : paramVo.getProps()) {
                //创建query对象
                BoolQueryBuilder query = QueryBuilders.boolQuery();
                String[] attrs = prop.split(":");
                query.must(QueryBuilders.termQuery("attrs.attrId",attrs[0]));
                query.must(QueryBuilders.termQuery("attrs.attrValue",attrs[1]));
                //ScoreMode.None 评分模式
                must.add(QueryBuilders.nestedQuery("attrs",query, ScoreMode.None));
            }
        }
        //=======================检索完成==================================

        //创建一个原生的DSL    query对象
        NativeSearchQuery dsl = new NativeSearchQuery(boolQuery);

        //2.分页查询
        PageRequest page = PageRequest.of(paramVo.getPageNo() - 1, paramVo.getPageSize());
        dsl.setPageable(page);
        //=======================分页完成==================================

        //3.排序信息
        if(!StringUtils.isEmpty(paramVo.getOrder())){
            String[] split = paramVo.getOrder().split(":");
            Sort sort = null;
            switch (split[0]){
                case "1":
                    sort = sort.by("hotScore");
                    sort = split[1].equals("asc") ? sort.ascending() : sort.descending();
                    break;
                case "2":
                    sort = Sort.by("price");
                    sort = split[1].equals("asc") ? sort.ascending() : sort.descending();
                    break;
            }
            dsl.addSort(sort);
        }
        //=======================排序完成==================================

        //4.聚合统计品牌的值和名
        TermsAggregationBuilder tmIdAgg = AggregationBuilders
                .terms("tmIdAgg")  //自定义的变量名 标识查询到的结果
                .field("tmId")  //根据什么值查询
                .size(200);

        //tmIdAgg有两个子聚合
        //品牌名子聚合
        TermsAggregationBuilder tmNameAgg = AggregationBuilders
                .terms("tmNameAgg")
                .field("tmName")
                .size(1);
        tmIdAgg.subAggregation(tmNameAgg);
        //品牌logo子聚合
        TermsAggregationBuilder tmLogoAgg = AggregationBuilders
                .terms("tmLogoAgg")
                .field("tmLogoUrl")
                .size(1);

        tmIdAgg.subAggregation(tmLogoAgg);

        dsl.addAggregation(tmIdAgg);

        //=======================聚合品牌完成==================================
        //6.统计属性的值和名
        //声明是一个嵌入式聚合
        NestedAggregationBuilder attrAggNested  = AggregationBuilders.nested("attrAgg", "attrs");
        //根据id聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders
                .terms("attrIdAgg")
                .field("attrs.attrId")
                .size(200);

        //id聚合下面有两个子聚合--属性名聚合,属性值聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders
                .terms("attrNameAgg")
                .field("attrs.attrName")
                .size(1);
        attrIdAgg.subAggregation(attrNameAgg);

        TermsAggregationBuilder attrValueAgg = AggregationBuilders
                .terms("attrValueAgg")
                .field("attrs.attrValue")
                .size(200);
        attrIdAgg.subAggregation(attrValueAgg);

        attrAggNested.subAggregation(attrIdAgg);
        dsl.addAggregation(attrAggNested);
        //===================聚合属性完成===========================
        //7.高亮设置
        if(!StringUtils.isEmpty(paramVo.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title")
                    .preTags("<span style='color:red'>")
                    .postTags("</span>");

            HighlightQuery highlightQuery = new HighlightQuery(highlightBuilder);
            dsl.setHighlightQuery(highlightQuery);
        }

        return dsl;
    }

    /**
     * 构建相应对象
     * @param search
     * @return
     */
    private SearchResponseVo buildResponse(SearchHits<Goods> search,SearchParamVo paramVo) {
        SearchResponseVo response = new SearchResponseVo();

        //1.检索条件
        response.setSearchParam(paramVo);
        //2.品牌面包屑,判断是否有品牌选择
        if(!StringUtils.isEmpty(paramVo.getTrademark())){  //trademark = 2:苹果
            response.setTrademarkParam("品牌:"+paramVo.getTrademark().split(":")[1]);
        }
        //3.属性面包屑
        String[] props = paramVo.getProps();
        if(props != null && props.length > 0){
            List<SearchAttr> list = Arrays.stream(props).map(item -> {
                String[] split = item.split(":");
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(Long.parseLong(split[0]));
                searchAttr.setAttrName(split[1]);
                searchAttr.setAttrValue(split[2]);
                return searchAttr;
            }).collect(Collectors.toList());
            response.setPropsParamList(list);
        }

        //4.品牌列表（4和5需要es聚合查询涉及到的所有可能）
        //ParsedLongTerms具体的类名定义:  parse + 查询字段类型 + 聚合类型
        ParsedLongTerms tmIdAgg = search.getAggregations().get("tmIdAgg");
        List<SearchTmVo> tmVos = tmIdAgg.getBuckets().stream()
                .map(item -> {
                    SearchTmVo searchTmVo = new SearchTmVo();
                    //设置tmid
                    searchTmVo.setTmId(item.getKeyAsNumber().longValue());
                    //设置品牌名
                    searchTmVo.setTmName(((ParsedStringTerms) item.getAggregations().get("tmNameAgg")).getBuckets().get(0).getKeyAsString());
                    //设置logo
                    searchTmVo.setTmLogoUrl(((ParsedStringTerms) item.getAggregations().get("tmLogoAgg")).getBuckets().get(0).getKeyAsString());
                    return searchTmVo;
                }).collect(Collectors.toList());
        response.setTrademarkList(tmVos);
        
        //5.属性列表
        ParsedNested attrAgg = search.getAggregations().get("attrAgg");
        //attrAgg下面的子聚合
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<SearchRespAttr> valueList = attrIdAgg.getBuckets().stream()
                .map(bucket -> {
                    SearchRespAttr searchRespAttr = new SearchRespAttr();
                    //设置属性id
                    searchRespAttr.setAttrId(bucket.getKeyAsNumber().longValue());
                    //设置属性名
                    String attrName = ((ParsedStringTerms) bucket.getAggregations().get("attrNameAgg")).getBuckets().get(0).getKeyAsString();
                    searchRespAttr.setAttrName(attrName);
                    //设置属性值
                    List<String> attrValueAgg = ((ParsedStringTerms) bucket.getAggregations().get("attrValueAgg")).getBuckets()
                            .stream()
                            .map(item -> {
                                String attrValue = ((Terms.Bucket) item).getKeyAsString();
                                return attrValue;
                            }).collect(Collectors.toList());
                    searchRespAttr.setAttrValueList(attrValueAgg);
                    return searchRespAttr;
                }).collect(Collectors.toList());
        response.setAttrsList(valueList);
        
        //6.排序信息
        String order = paramVo.getOrder();
        SearchOrderMapVo mapVo = new SearchOrderMapVo("1", "desc");
        if(!StringUtils.isEmpty(order) && order.contains(":")){
            mapVo.setType(order.split(":")[0]);
            mapVo.setSort(order.split(":")[1]);
        }
        response.setOrderMap(mapVo);
        //7.商品列表
        List<Goods> goodsList = search.getSearchHits()
                .stream().map(goodsSearchHit -> {
                    Goods goods = goodsSearchHit.getContent();
                    if(!StringUtils.isEmpty(paramVo.getKeyword())){
                        //如果有keyword检索,就要用高亮title
                        List<String> title = goodsSearchHit.getHighlightField("title");
                        goods.setTitle(title.get(0));
                    }
                    return goods;
                }).collect(Collectors.toList());
        response.setGoodsList(goodsList);


        //8.当前页码
        response.setPageNo(paramVo.getPageNo());

        //9.总页码  根据查询到的记录数运算 总记录数/pagesize
        long totalHits = search.getTotalHits();
        Long totalPages = totalHits%paramVo.getPageSize()==0?totalHits/paramVo.getPageSize():totalHits/paramVo.getPageSize()+1;
        response.setTotalPages(totalPages);

        //10.url参数
        String url = makeUrlParam(paramVo);
        response.setUrlParam(url);


        return response;
    }

    /**
     * 还原出请求参数
     * @return
     */
    private String makeUrlParam(SearchParamVo paramVo) {
        StringBuilder url = new StringBuilder("list.html?");
        //第一个&可加可不加
        if (paramVo.getCategory1Id() != null) {
            url.append("&category1Id=" + paramVo.getCategory1Id());
        }

        if (paramVo.getCategory2Id() != null) {
            url.append("&category2Id=" + paramVo.getCategory2Id());
        }

        if (paramVo.getCategory3Id() != null) {
            url.append("&category3Id=" + paramVo.getCategory3Id());
        }

        if (!StringUtils.isEmpty(paramVo.getKeyword())) {
            url.append("&keyword=" + paramVo.getKeyword());
        }

        if (!StringUtils.isEmpty(paramVo.getTrademark())) {
            url.append("&trademark=" + paramVo.getTrademark());
        }

        //属性
        if (paramVo.getProps() != null && paramVo.getProps().length > 0) {
            for (String prop : paramVo.getProps()) {
                url.append("&props=" + prop);
            }
        }

        return url.toString();
    }
}
